package org.gdstash.db;

import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import org.gdstash.description.DetailComposer;
import org.gdstash.file.ARZRecord;
import org.gdstash.file.GDParseException;
import org.gdstash.util.GDColor;
import org.gdstash.util.GDConstants;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class DBPet {
    public static final String TABLE_NAME = "GD_PET";
    public static final String FIELD_ID = "PET_ID";
    private static final int ROW_PET_ID = 1;
    private static final int ROW_NAME = 2;
    private static final int ROW_FORMULA_LEVEL = 3;
    private static final int ROW_BIO_ID = 4;
    private static final int ROW_SKILL_DIE = 5;
    private static ConcurrentHashMap<String, DBPet> hashBuffer;
    private String petID;
    private String name;
    private String formulaLevel;
    private String bioID;
    private String dieSkillID;
    private List<DBPetSkill> skills;
    private DBPetBio dbBio;
    private Expression expressionLevel;
    private int petLevel;

    private DBPet() {
    }

    private DBPet(final ARZRecord record) {
        this.petID = record.getFileName();
        this.name = record.getPetName();
        this.formulaLevel = record.getPetFormulaLevel();
        this.bioID = record.getPetBioID();
        this.dieSkillID = record.getPetDieSkillID();
        this.skills = record.getPetSkillList();
    }

    static {
        DBPet.hashBuffer = new ConcurrentHashMap<String, DBPet>();
    }

    private static List<DBPet> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBPet> list = new LinkedList<DBPet>();
        while (rs.next()) {
            final DBPet pet = new DBPet();
            pet.petID = rs.getString(1);
            pet.name = rs.getString(2);
            pet.formulaLevel = rs.getString(3);
            pet.bioID = rs.getString(4);
            pet.dieSkillID = rs.getString(5);
            pet.skills = DBPetSkill.getByPetID(pet.petID);
            if (pet.formulaLevel != null) {
                pet.formulaLevel = pet.formulaLevel.toUpperCase(GDConstants.LOCALE_US);
            }
            try {
                pet.setLevelFormula(pet.formulaLevel);
            } catch (GDParseException ex) {
            }
            if (pet.bioID != null) {
                pet.dbBio = DBPetBio.get(pet.bioID);
            }
            pet.setPlayerLevel(1);
            if (pet.name != null) {
                final ByteBuffer buffer = StandardCharsets.UTF_8.encode(pet.name);
                final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                pet.name = utf8;
            }
            list.add(pet);
        }
        return list;
    }

    public String getName() {
        return this.name;
    }

    private int getPetLevelByPlayerLevel(final int level) {
        int lvl = 1;
        if (level > lvl) {
            lvl = level;
        }
        this.setCharLevel(lvl);
        return (int) (this.getPetCharLevel() + 0.1);
    }

    private void setCharLevel(final int level) {
        if (this.expressionLevel == null) {
            return;
        }
        this.expressionLevel.setVariable("CHARLEVEL", (double) level);
    }

    private int getPetCharLevel() {
        if (this.expressionLevel == null) {
            return 1;
        }
        final double value = this.expressionLevel.evaluate();
        return (int) (value + 0.1);
    }

    public int getHealth() {
        if (this.dbBio == null) {
            return 0;
        }
        return this.dbBio.getLifeByPetLevel(this.getPetCharLevel());
    }

    public int getMana() {
        if (this.dbBio == null) {
            return 0;
        }
        return this.dbBio.getManaByPetLevel(this.getPetCharLevel());
    }

    public String getPetSkillDescriptions() {
        String s = "";
        for (final DBPetSkill psk : this.skills) {
            final int lvl = psk.getSkillLevelByPetLevel(this.petLevel);
            if (lvl <= 0) {
                continue;
            }
            final DBSkill sk = psk.getDBSkill();
            if (sk == null) {
                continue;
            }
            if (sk.getName() == null) {
                continue;
            }
            if (!s.isEmpty()) {
                s += "<br>";
            }
            if (sk.getSkillID().equals(this.dieSkillID)) {
                s = s + GDColor.HTML_COLOR_COMPONENT + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "TXT_PET_ON_DEATH") + "</font>" + " ";
            }
            s += sk.getSkillDescription(lvl, 100, false);
        }
        return s;
    }

    public static void clearBuffer() {
        DBPet.hashBuffer.clear();
    }

    public static void createTables() throws SQLException {
        final String dropTable = "DROP TABLE GD_PET";
        final String createTable = "CREATE TABLE GD_PET (PET_ID  VARCHAR(256) NOT NULL, NAME              VARCHAR(256), FORMULA_LEVEL     VARCHAR(256), BIO_ID            VARCHAR(256), DIE_SKILL_ID      VARCHAR(256), PRIMARY KEY (PET_ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GD_PET")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
                DBPetSkill.createTable(conn);
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GD_PET"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBPet entry = get(record.getFileName());
        if (entry != null) {
            return;
        }
        final DBPet pet = new DBPet(record);
        if (pet.formulaLevel == null) {
            return;
        }
        try {
            final String formula = pet.formulaLevel.toUpperCase(GDConstants.LOCALE_US);
            ExpressionBuilder builder = new ExpressionBuilder(formula);
            if (formula.contains("CHARLEVEL")) {
                builder = builder.variables(new String[]{"CHARLEVEL"});
            }
            final Expression expression = builder.build();
            if (formula.contains("CHARLEVEL")) {
                expression.setVariable("CHARLEVEL", 1.0);
            }
            expression.evaluate();
        } catch (Throwable ex) {
            final Object[] args = {record.getFileName()};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_FORMULA_BY_ID", args);
            GDMsgLogger.addWarning(msg);
            GDMsgLogger.addWarning(ex);
            return;
        }
        final String insert = "INSERT INTO GD_PET VALUES (?,?,?,?,?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insert)) {
                ps.setString(1, pet.petID);
                ps.setString(2, pet.name);
                ps.setString(3, pet.formulaLevel);
                ps.setString(4, pet.bioID);
                ps.setString(5, pet.dieSkillID);
                ps.executeUpdate();
                ps.close();
                DBPetSkill.insert(conn, pet.petID, pet.skills);
                conn.commit();
            } catch (SQLException ex2) {
                conn.rollback();
                final Object[] args2 = {record.getFileName(), "GD_PET"};
                final String msg2 = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_INS_TABLE_BY_ID", args2);
                GDMsgLogger.addLowError(msg2);
                GDMsgLogger.addLowError(ex2);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static DBPet get(final String petID) {
        if (petID == null) {
            return null;
        }
        DBPet pet = null;
        pet = DBPet.hashBuffer.get(petID);
        if (pet == null) {
            pet = getDB(petID);
            if (pet != null) {
                DBPet.hashBuffer.put(pet.petID, pet);
            }
        }
        return pet;
    }

    private static DBPet getDB(final String petID) {
        DBPet pet = null;
        final String command = "SELECT * FROM GD_PET WHERE PET_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, petID);
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBPet> list = wrap(rs);
                if (list.isEmpty()) {
                    pet = null;
                } else {
                    pet = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {petID, "GD_PET"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return pet;
    }

    public void setPlayerLevel(final int level) {
        this.petLevel = this.getPetLevelByPlayerLevel(level);
    }

    public String getPetDescription(final boolean fullHTML) {
        return DetailComposer.getPetDescription(this, fullHTML);
    }

    private void setLevelFormula(final String formulaLevel) throws GDParseException {
        this.formulaLevel = formulaLevel;
        if (formulaLevel == null) {
            this.expressionLevel = null;
        } else {
            ExpressionBuilder builder = new ExpressionBuilder(formulaLevel);
            if (formulaLevel.contains("CHARLEVEL")) {
                builder = builder.variables(new String[]{"CHARLEVEL"});
            }
            try {
                this.expressionLevel = builder.build();
            } catch (Throwable ex) {
                this.expressionLevel = null;
                throw new GDParseException(ex.getMessage());
            }
        }
    }
}
